{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Using Interval Variables and AddNoOverlap2D constraint for 2D rectangle packing\n",
    "\n",
    "Given a rectangular container and a set of rectangular items, the goal is to pack all items into the container respecting the orientation of items.\n",
    "All rectangles are orthogonal, i.e. they are parallel to the axes of the container.\n",
    "Thus, each item is defined by its length and width.\n",
    "\n",
    "This problem is also known as the 2D bin packing problem and is NP-hard.\n",
    "With CP-SAT, we can easily model this problem and solve smaller instances to optimality."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ortools.sat.python import cp_model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Instance\n",
    "container = (40, 15)\n",
    "boxes = [\n",
    "    (11, 3),\n",
    "    (13, 3),\n",
    "    (9, 2),\n",
    "    (7, 2),\n",
    "    (9, 3),\n",
    "    (7, 3),\n",
    "    (11, 2),\n",
    "    (13, 2),\n",
    "    (11, 4),\n",
    "    (13, 4),\n",
    "    (3, 5),\n",
    "    (11, 2),\n",
    "    (2, 2),\n",
    "    (11, 3),\n",
    "    (2, 3),\n",
    "    (5, 4),\n",
    "    (6, 4),\n",
    "    (12, 2),\n",
    "    (1, 2),\n",
    "    (3, 5),\n",
    "    (13, 5),\n",
    "    (12, 4),\n",
    "    (1, 4),\n",
    "    (5, 2),\n",
    "    # (6,  2),  # add to make tight\n",
    "    # (6,3), # add to make infeasible\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = cp_model.CpModel()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<ortools.sat.python.cp_model.Constraint at 0x7fe4d5125400>"
      ]
     },
     "execution_count": 69,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# We have to create the variable for the bottom left corner of the boxes.\n",
    "# We directly limit their range, such that the boxes are inside the container\n",
    "x_vars = [\n",
    "    model.new_int_var(0, container[0] - box[0], name=f\"x1_{i}\")\n",
    "    for i, box in enumerate(boxes)\n",
    "]\n",
    "y_vars = [\n",
    "    model.new_int_var(0, container[1] - box[1], name=f\"y1_{i}\")\n",
    "    for i, box in enumerate(boxes)\n",
    "]\n",
    "# Interval variables are actually more like constraint containers, that are then passed to the no overlap constraint\n",
    "# Note that we could also make size and end variables, but we don't need them here\n",
    "x_interval_vars = [\n",
    "    model.new_interval_var(\n",
    "        start=x_vars[i], size=box[0], end=x_vars[i] + box[0], name=f\"x_interval_{i}\"\n",
    "    )\n",
    "    for i, box in enumerate(boxes)\n",
    "]\n",
    "y_interval_vars = [\n",
    "    model.new_interval_var(\n",
    "        start=y_vars[i], size=box[1], end=y_vars[i] + box[1], name=f\"y_interval_{i}\"\n",
    "    )\n",
    "    for i, box in enumerate(boxes)\n",
    "]\n",
    "# Enforce that no two rectangles overlap\n",
    "model.add_no_overlap_2d(x_interval_vars, y_interval_vars)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Starting CP-SAT solver v9.3.10497\n",
      "Parameters: log_search_progress: true\n",
      "Setting number of workers to 8\n",
      "\n",
      "Initial satisfaction model '':\n",
      "#Variables: 96\n",
      "  - 3 in [0,10]\n",
      "  - 6 in [0,11]\n",
      "  - 6 in [0,12]\n",
      "  - 9 in [0,13]\n",
      "  - 24 in [0,15]\n",
      "  - 4 in [0,27]\n",
      "  - 2 in [0,28]\n",
      "  - 5 in [0,29]\n",
      "  - 2 in [0,31]\n",
      "  - 2 in [0,33]\n",
      "  - 1 in [0,34]\n",
      "  - 2 in [0,35]\n",
      "  - 2 in [0,37]\n",
      "  - 2 in [0,38]\n",
      "  - 2 in [0,39]\n",
      "  - 24 in [0,40]\n",
      "#kInterval: 48\n",
      "#kLinear1: 48\n",
      "#kNoOverlap2D: 1 (#rectangles: 24)\n",
      "\n",
      "Starting presolve at 0.01s\n",
      "[ExtractEncodingFromLinear] #potential_supersets=0 #potential_subsets=0 #at_most_one_encodings=0 #exactly_one_encodings=0 #unique_terms=0 #multiple_terms=0 #literals=0 time=3e-06s\n",
      "[Probing] deterministic_time: 0 (limit: 1) wall_time: 1.7e-05 (0/0)\n",
      "[DetectDuplicateConstraints] #duplicates=0 time=1.3e-05s\n",
      "[DetectDominatedLinearConstraints] #relevant_constraints=0 #work_done=0 #num_inclusions=0 #num_redundant=0 time=4e-06s\n",
      "[DetectOverlappingColumns] #processed_columns=0 #work_done=0 #nz_reduction=0 time=1.1e-05s\n",
      "[ProcessSetPPC] #relevant_constraints=0 #num_inclusions=0 work=0 time=1.2e-05s\n",
      "[Symmetry] Graph for symmetry has 241 nodes and 288 arcs.\n",
      "[Symmetry] Symmetry computation done. time: 0.000173 dtime: 5.969e-05\n",
      "[Symmetry] #generators: 3, average support size: 4\n",
      "[Symmetry] 6 orbits with sizes: 2,2,2,2,2,2\n",
      "[Symmetry] Found orbitope of size 2 x 2\n",
      "\n",
      "Presolve summary:\n",
      "  - 0 affine relations were detected.\n",
      "  - rule 'TODO symmetry: add symmetry breaking inequalities?' was applied 1 time.\n",
      "  - rule 'linear: always true' was applied 48 times.\n",
      "  - rule 'linear: fixed or dup variables' was applied 48 times.\n",
      "  - rule 'presolve: 48 unused variables removed.' was applied 1 time.\n",
      "  - rule 'presolve: iteration' was applied 1 time.\n",
      "\n",
      "Presolved satisfaction model '':\n",
      "#Variables: 48\n",
      "  - 3 in [0,10]\n",
      "  - 6 in [0,11]\n",
      "  - 6 in [0,12]\n",
      "  - 9 in [0,13]\n",
      "  - 4 in [0,27]\n",
      "  - 2 in [0,28]\n",
      "  - 5 in [0,29]\n",
      "  - 2 in [0,31]\n",
      "  - 2 in [0,33]\n",
      "  - 1 in [0,34]\n",
      "  - 2 in [0,35]\n",
      "  - 2 in [0,37]\n",
      "  - 2 in [0,38]\n",
      "  - 2 in [0,39]\n",
      "#kInterval: 48\n",
      "#kNoOverlap2D: 1 (#rectangles: 24)\n",
      "\n",
      "Preloading model.\n",
      "[Symmetry] Graph for symmetry has 193 nodes and 288 arcs.\n",
      "[Symmetry] Symmetry computation done. time: 0.000159 dtime: 5.681e-05\n",
      "[Symmetry] #generators: 3, average support size: 4\n",
      "[Symmetry] Found orbitope of size 2 x 2\n",
      "\n",
      "Starting Search at 0.02s with 8 workers.\n",
      "7 full subsolvers: [default_lp, less_encoding, no_lp, max_lp, quick_restart, quick_restart_no_lp, lb_tree_search]\n",
      "Interleaved subsolvers: [feasibility_pump, rins_lns_default, rens_lns_default]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "#1       7.76s less_encoding fixed_bools:0/722\n",
      "\n",
      "Sub-solver search statistics:\n",
      "  'max_lp':\n",
      "     LP statistics:\n",
      "       final dimension: 11 rows, 48 columns, 161 entries with magnitude in [1.924501e-01, 1.000000e+00]\n",
      "       total number of simplex iterations: 4967\n",
      "       num solves: \n",
      "         - #OPTIMAL: 19093\n",
      "       managed constraints: 82\n",
      "       merged constraints: 40\n",
      "       coefficient strenghtenings: 1\n",
      "       total cuts added: 82 (out of 122 calls)\n",
      "         - 'CG': 14\n",
      "         - 'MIR_1': 28\n",
      "         - 'NoOverlap2dXCompletionTime': 30\n",
      "         - 'NoOverlap2dYCompletionTime': 8\n",
      "         - 'ZERO_HALF': 2\n",
      "\n",
      "  'lb_tree_search':\n",
      "     LP statistics:\n",
      "       final dimension: 11 rows, 48 columns, 163 entries with magnitude in [3.038218e-01, 1.000000e+00]\n",
      "       total number of simplex iterations: 5009\n",
      "       num solves: \n",
      "         - #OPTIMAL: 19210\n",
      "         - #DUAL_FEASIBLE: 2\n",
      "       managed constraints: 84\n",
      "       merged constraints: 41\n",
      "       coefficient strenghtenings: 1\n",
      "       total cuts added: 84 (out of 125 calls)\n",
      "         - 'CG': 14\n",
      "         - 'MIR_1': 29\n",
      "         - 'NoOverlap2dXCompletionTime': 31\n",
      "         - 'NoOverlap2dYCompletionTime': 8\n",
      "         - 'ZERO_HALF': 2\n",
      "\n",
      "\n",
      "CpSolverResponse summary:\n",
      "status: OPTIMAL\n",
      "objective: NA\n",
      "best_bound: NA\n",
      "booleans: 683\n",
      "conflicts: 3560\n",
      "branches: 28275\n",
      "propagations: 215010\n",
      "integer_propagations: 67169\n",
      "restarts: 51\n",
      "lp_iterations: 0\n",
      "walltime: 7.80482\n",
      "usertime: 7.80482\n",
      "deterministic_time: 0.679965\n",
      "gap_integral: 0\n",
      "\n",
      "box 0 is placed at (18, 12)\n",
      "box 1 is placed at (14, 0)\n",
      "box 2 is placed at (17, 7)\n",
      "box 3 is placed at (6, 5)\n",
      "box 4 is placed at (31, 10)\n",
      "box 5 is placed at (6, 2)\n",
      "box 6 is placed at (29, 13)\n",
      "box 7 is placed at (2, 9)\n",
      "box 8 is placed at (26, 5)\n",
      "box 9 is placed at (13, 3)\n",
      "box 10 is placed at (37, 5)\n",
      "box 11 is placed at (3, 0)\n",
      "box 12 is placed at (0, 9)\n",
      "box 13 is placed at (20, 9)\n",
      "box 14 is placed at (3, 2)\n",
      "box 15 is placed at (0, 5)\n",
      "box 16 is placed at (0, 11)\n",
      "box 17 is placed at (5, 7)\n",
      "box 18 is placed at (26, 3)\n",
      "box 19 is placed at (0, 0)\n",
      "box 20 is placed at (27, 0)\n",
      "box 21 is placed at (6, 11)\n",
      "box 22 is placed at (5, 2)\n",
      "box 23 is placed at (15, 9)\n"
     ]
    }
   ],
   "source": [
    "# Solve!\n",
    "solver = cp_model.CpSolver()\n",
    "solver.parameters.log_search_progress = True\n",
    "solver.log_callback = print\n",
    "status = solver.solve(model)\n",
    "assert status == cp_model.OPTIMAL\n",
    "for i, box in enumerate(boxes):\n",
    "    print(\n",
    "        f\"box {i} is placed at ({solver.value(x_vars[i])}, {solver.value(y_vars[i])})\"\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYAAAACgCAYAAAAFHLs9AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAPDklEQVR4nO3df4wc9X3G8fdTB7f0Qg0E8yPYCbSyqQkKhJ5MIipEmoLOLsVJhSpbbaAklUOEpaBGKtDIKW3/QW0StQkR1AlWoSVGrcDBiuyARVuRSCXl7BgwXMCGEmFsYRMag00k1/D0jx2j5dj13c3s3s55npe02vnxnZnPfXdvP9rvzsxHtomIiOb5pUEHEBERg5EEEBHRUEkAERENlQQQEdFQSQAREQ2VBBAR0VATJgBJayXtlbS9bdktkl6StK14LO2y7YikZyTtlHRTLwOPiIhqNNF1AJIuAQ4Ad9s+r1h2C3DA9leOst0s4FngMmAX8BiwwvbTvQk9IiKqmPAbgO1HgFdL7HsxsNP287YPAfcCy0rsJyIi+uA9FbZdJelqYBT4ou3/Hbf+TODFtvldwEXddiZpJbAS4Pjjh35r3rzfrBBaPRw6BLNnDzqKOCKvx/RKf0+vHTu2vGJ77lS2KZsAbgf+BnDx/FXgM+PaqMN2XcebbK8B1gAsXDjsdetGS4ZWH5s2wZIlg44ijsjrMb3S39NreFg/neo2pc4Csv2y7TdtvwV8i9Zwz3i7gPlt8/OA3WWOFxERvVcqAUg6o232U8D2Ds0eAxZIOlvSbGA5sKHM8SIiovcmHAKStA64FDhF0i7gL4FLJV1Aa0jnBeBzRdv3A9+2vdT2YUmrgAeBWcBa20/144+IiIipmzAB2F7RYfGdXdruBpa2zW8ENpaOLiIi+iZXAkdENFQSQEREQyUBREQ0VBJARERDJQFERDRUEkBEREMlAURENFQSQEREQyUBREQ0VBJARERDJQFERDRUlYIwERFdDQ3Bvn3V9jE2BgcP9iaeeLfJ3A10LXAFsLetJvDfAb8PHAKeA661/fMO274AvA68CRy2PdyzyCOi1hYtgpGR6vuZO6UaV821evXUt5nMENA/AeNfxs3AebY/TKvw+81H2f7jti/Ih39ERL2UKgpv+yHbh4vZR2lV+4qIiBmkFz8CfwbY1GWdgYckbSmKvnclaaWkUUmj+/dXHDiMiIgJVUoAkr4EHAbu6dLkYtsXAkuA6yVd0m1fttfYHrY9PGdOBv0iIvqtdAKQdA2tH4f/yLY7tSkqhGF7L7CezsXjIyJiAMoWhR8BbgSutP1GlzZDkk44Mg1cTufi8RERMQBli8LfDPwysFkSwKO2r2svCg+cBqwv1r8H+I7t7/flr4iYhJyXPr2GhqrvY2wsp4H2U9+Kwtt+Hji/UnQRPZTz0mee0dFBR3Bsy60gIiIaKgkgIqKhkgAiIhoqCSAioqGSACIiGioJICKioZIAIiIaqpYFYQ4dgk3dbi83Cfv21eNc7X37qv0dTTIdr1kuTIp4p1omgNmzYcmS8ttv2lRt+5h+M+U1y4VJcSzJEFBEREMlAURENFQSQEREQ02YACStlbRX0va2ZSdL2ixpR/F8UpdtRyQ9I2mnpJt6GXhERFRTtij8TcDDthcADxfz7yBpFvBNWtXAzgVWSDq3UrQREdEzpYrCA8uAu4rpu4BPdth0MbDT9vO2DwH3FttFREQNlD0N9DTbewBs75F0aoc2ZwIvts3vAi7qtsOiaPxKgFNP/UDJsCLiWNKLIj51UNdCQv28DkAdlnWsHQytovDAGoCFC4e7touI5uhVEZ866PcFhKtXT32bsmcBvSzpDIDieW+HNruA+W3z84DdJY8XERE9VjYBbACuKaavAR7o0OYxYIGksyXNBpYX20VERA1M5jTQdcB/AedI2iXps8CtwGWSdgCXFfNIer+kjQC2DwOrgAeBMeBfbT/Vnz8jIiKmqmxReIBPdGj7dlH4Yn4jsLF0dBER0Te5EjgioqGSACIiGqqWt4PuRT2Aft+Hvy41B44VvXjNpuM1OVZqPMyU928vajjUQV3rSNQyAVStBzAdZsr965skr8nkpa+mV13rSGQIKCKioZIAIiIaKgkgIqKhkgAiIhoqCSAioqGSACIiGioJICKioZIAIiIaqnQCkHSOpG1tj9ck3TCuzaWS9re1+XLliCMioidKXwls+xngAni7APxLwPoOTX9g+4qyx4mIiP7o1RDQJ4DnbP+0R/uLiIg+61UCWA6s67LuY5Iel7RJ0oe67UDSSkmjkkb37z8GqkBHRNRc5QRQlHu8Evi3Dqu3Ah+0fT7wDeC73fZje43tYdvDc+bU8LZ5ERHHmF58A1gCbLX98vgVtl+zfaCY3ggcJ+mUHhwzIiIq6kUCWEGX4R9Jp0tSMb24ON7PenDMiIioqFI9AEm/Sqso/Ofall0HYPsO4Crg85IOA78Altt2lWPWxdBQq6jG0YyNwcGD0xPPINWluMixUqxlOkz03o1mqJQAbL8BvG/csjvapm8DbqtyjLpatAhGRiZuV4cPxn5LcZGZJ4kyIFcCR0Q0VhJARERDJQFERDRUEkBEREMlAURENFQSQEREQyUBREQ0VKXrAJpsbGxybZpwHUBEzExJACUdPDjxh/vo6PTEEhFRRoaAIiIaKgkgIqKhkgAiIhqqUgKQ9IKkJ4uC7+8a8VbL1yXtlPSEpAurHC8iInqnFz8Cf9z2K13WLQEWFI+LgNuL54iIGLB+DwEtA+52y6PAiZLO6PMxIyJiEqp+AzDwkCQD/2h7zbj1ZwIvts3vKpbtGb8jSSuBlQAnn/yBvt+vvGoRk8kU1GhKgZIUF5l5JvPerEuhn2NBXT8LqiaAi23vlnQqsFnST2w/0rZeHbbpWBGsSB5rABYuHHa/C4xULWIymRdz7txmFEqp4xs7jm4y780U+plZVq+e+jaVhoBs7y6e9wLrgcXjmuwC5rfNzwN2VzlmRET0RukEIGlI0glHpoHLge3jmm0Ari7OBvoosN/2u4Z/IiJi+lUZAjoNWC/pyH6+Y/v744rCbwSWAjuBN4Brq4UbERG9UjoB2H4eOL/D8vai8AauL3uMiIjon1wJHBHRUEkAERENlQQQEdFQSQAREQ2VBBAR0VBJABERDZUEEBHRUEkAERENlQQQEdFQSQAREQ2VBBAR0VC9KAkZDTc0NHFRmLExOHhweuKJiU22IEwc20onAEnzgbuB04G3gDW2/2Fcm0uBB4D/KRbdb/uvyx4z6mnRIhgZmbhdqkvNLCn0c+yr8g3gMPBF21uLugBbJG22/fS4dj+wfUWF40RERB+U/g3A9h7bW4vp14ExWvV+IyJiBujJj8CSzgI+Avyow+qPSXpc0iZJHzrKPlZKGpU0un9/Bh8jIvqtcgKQ9F7gPuAG26+NW70V+KDt84FvAN/tth/ba2wP2x6eMyeDxRER/VYpAUg6jtaH/z227x+/3vZrtg8U0xuB4ySdUuWYERHRG1WKwgu4Exiz/bUubU4v2iFpcXG8n5U9ZkRE9E6Vs4AuBj4NPClpW7HsL4APwNu1ga8CPi/pMPALYHlRJ3jgJnMedNXtqx5jphgamrjN2FhOA42omypF4X8IaII2twG3lT1GP82dC0uWDDqK5hgdHXQEETFebgUREdFQSQAREQ2VBBAR0VBJABERDZUEEBHRUEkAERENlQQQEdFQSQAREQ2VBBAR0VBJABERDZUEEBHRUEkAERENVbUewIikZyTtlHRTh/WS9PVi/ROSLqxyvIiI6J0q9QBmAd8ElgDnAisknTuu2RJgQfFYCdxe9ngREdFbVb4BLAZ22n7e9iHgXmDZuDbLgLvd8ihwoqQzKhwzIiJ6RGXrs0i6Chix/afF/KeBi2yvamvzPeDWonYAkh4GbrT9rrvDS1pJ61sCwHnA9lKBTZ9TgFcGHcQkJM7eSpy9lTh75xzbJ0xlgyoVwToVgxmfTSbTprXQXgOsAZA0anu4Qmx9NxNihMTZa4mztxJn70iactmlKkNAu4D5bfPzgN0l2kRExABUSQCPAQsknS1pNrAc2DCuzQbg6uJsoI8C+23vqXDMiIjokSo1gQ9LWgU8CMwC1tp+StJ1xfo7gI3AUmAn8AZw7SR3v6ZsXNNoJsQIibPXEmdvJc7emXKMpX8EjoiImS1XAkdENFQSQEREQ9UqAUx0a4m6kPSCpCclbStz6lW/SForaa+k7W3LTpa0WdKO4vmkQcZYxNQpzlskvVT06TZJSwcc43xJ/yFpTNJTkr5QLK9Vfx4lzrr1569I+m9Jjxdx/lWxvG792S3OWvVnEdMsST8urrcq1Ze1+Q2guLXEs8BltE4ffQxYYfvpgQbWgaQXgGHbtbowRNIlwAFaV1+fVyz7W+BV27cWSfUk2zfWMM5bgAO2vzLI2I4orlg/w/ZWSScAW4BPAn9CjfrzKHH+IfXqTwFDtg9IOg74IfAF4A+oV392i3OEGvUngKQ/A4aBX7N9RZn/9Tp9A5jMrSXiKGw/Arw6bvEy4K5i+i5aHw4D1SXOWrG9x/bWYvp1YAw4k5r151HirJXidjAHitnjioepX392i7NWJM0Dfg/4dtviKfdlnRLAmcCLbfO7qOEbuWDgIUlbiltY1NlpR669KJ5PHXA8R7OquGvs2kEPBbSTdBbwEeBH1Lg/x8UJNevPYshiG7AX2Gy7lv3ZJU6oV3/+PfDnwFtty6bcl3VKAJO+bUQNXGz7Qlp3O72+GNKIam4HfgO4ANgDfHWg0RQkvRe4D7jB9muDjqebDnHWrj9tv2n7Alp3BFgs6bwBh9RRlzhr05+SrgD22t5SdV91SgAz5rYRtncXz3uB9bSGr+rq5WKc+Mh48d4Bx9OR7ZeLf7y3gG9Rgz4txoDvA+6xfX+xuHb92SnOOvbnEbZ/DvwnrXH12vXnEe1x1qw/LwauLH6LvBf4HUn/Qom+rFMCmMytJQZO0lDxYxuShoDLqfedSzcA1xTT1wAPDDCWrvTO24R/igH3afFj4J3AmO2vta2qVX92i7OG/TlX0onF9PHA7wI/oX792THOOvWn7Zttz7N9Fq3PyX+3/ceU6UvbtXnQum3Es8BzwJcGHU+XGH8deLx4PFWnOIF1tL6e/h+tb1SfBd4HPAzsKJ5Prmmc/ww8CTxRvJHPGHCMv01rCPIJYFvxWFq3/jxKnHXrzw8DPy7i2Q58uVhet/7sFmet+rMt3kuB75Xty9qcBhoREdOrTkNAERExjZIAIiIaKgkgIqKhkgAiIhoqCSAioqGSACIiGioJICKiof4fTZUbO73YN68AAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# plot the solution\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.patches as patches\n",
    "\n",
    "fig, ax = plt.subplots(1)\n",
    "ax.set_xlim(0, container[0])\n",
    "ax.set_ylim(0, container[1])\n",
    "for i, box in enumerate(boxes):\n",
    "    ax.add_patch(\n",
    "        patches.Rectangle(\n",
    "            (solver.value(x_vars[i]), solver.value(y_vars[i])),\n",
    "            box[0],\n",
    "            box[1],\n",
    "            facecolor=\"blue\",\n",
    "            alpha=0.2,\n",
    "            edgecolor=\"b\",\n",
    "        )\n",
    "    )\n",
    "# uniform axis\n",
    "ax.set_aspect(\"equal\", adjustable=\"box\")\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "mo",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
